package com.yc.spring.aop;

import com.yc.spring.IocConfig;
import com.yc.spring.MyAnnotationConfigApplicationContext;
import com.yc.spring.bbs.dao.UserDao;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.SourceLocation;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AopApplicationContext extends MyAnnotationConfigApplicationContext {

    // 切面对象集合
    Map<String,Object> aspectMap = new HashMap<>();
    // 切点=>增强方法
    Map<String, List<Method>> pointcutMap = new HashMap<>();
    // Map<String, Map<String,List<Method>>> pointcutMap = new HashMap<>();
    // 需要动态代理的对象集合
    Map<String,Object> proxyBeanMap = new HashMap<>();

    public AopApplicationContext(Class configClass) throws NoSuchMethodException {
        // 思考题: 为什么编译器强制要求我们定义该构造函数?
        super(configClass);

        // 查找组件中的切面类 => @Aspect 注解
        this.singletonObjects.forEach((id,bean)->{
            // 判断切面类
            if(bean.getClass().getAnnotation(Aspect.class)!=null){
                aspectMap.put(id,bean);
            }
        });

        // 统计切点与增强 Pointcut + Advice, 封装切面信息对象, 保存切面集合中
        for (Object aspectBean : aspectMap.values()) {
            for (Method method : aspectBean.getClass().getDeclaredMethods()) {
                // 只实现前置增强
                Before before = method.getAnnotation(Before.class);
                if (before!=null) {
                    // 提取切点表达式
                    String value = before.value();
                    String pointcut = null;
                    if(value.matches("\\w+\\(\\)")){
                        // @After("myPointcut()")
                        String $1 = value.replaceAll("(\\w)\\(\\)", "$1");
                        // 获取切点方法
                        Method pointcutMethod = aspectBean.getClass().getDeclaredMethod($1);
                        // 获取切点方法上的切点
                        Pointcut pointcutAnno = pointcutMethod.getAnnotation(Pointcut.class);
                        pointcut = pointcutAnno.value();
                    } else {
                        // @Before("execution(* com.yc..dao.UserDaoImpl.*(..))")
                        pointcut = value;
                    }
                    // 将切点 和 增强方法 添加到集合中
                    List<Method> list = pointcutMap.get(pointcut);
                    // 如果 list 为空, 那么是第一次添加
                    if(list==null){
                        list = new ArrayList<>();
                        pointcutMap.put(pointcut, list);
                    }
                    list.add(method);
                }

            }
        }

        // 扫描所有的组件, 查看是否包含被切点拦截的方法
        this.singletonObjects.forEach((id,bean)->{
            for (Method method : bean.getClass().getDeclaredMethods()) {
                a: for (String pointcut : pointcutMap.keySet()) {
                    if (isPointcut(pointcut,method)) {
                        proxyBeanMap.put(id,bean);
                        // 只要有一个方法被切到, 那么就要代理, 即可终止循环判断
                        break a;
                    }
                }
            }
        });

        // 如果组件包含被切点拦截的方法, 那么生成动态代理对象替换原有的组件
        proxyBeanMap.forEach((id,bean)->{
            Class<?>[] interfaces = bean.getClass().getInterfaces();
            if(interfaces==null || interfaces.length==0){
                // cglib 动态代理
            } else {
                // jdk动态代理
                jdkProxy(id,bean);
            }
        });

    }

    private void jdkProxy(String id, Object bean) {
        // 动态生产代理对象
        Object proxyBean = Proxy.newProxyInstance(
                bean.getClass().getClassLoader(),
                bean.getClass().getInterfaces(),
                (proxy, method, args)->{
                    // JDK stream 编程
                    try{
                        // 前置增强
                        pointcutMap
                                // 双列集合转单列集合  Set<Entry<pointcut, list<method>>>
                                .entrySet()
                                // 集合转成 流
                                .stream()
                                // 过滤切点
                                .filter(entry->isPointcut(entry.getKey(),method))
                                // 将 map里面的list的元素提取出来
                                .flatMap(entry->entry.getValue().stream())
                                // 过滤出前置拦截方法集合
                                .filter(adviceMethod->adviceMethod.getAnnotation(Before.class)!=null)
                                // 执行所有的前置增强方法
                                .forEach(beforeMethod->{
                                    // 取出唯一个 切面对象
                                    Object myAspect = aspectMap.get("myAspect");
                                    try {
                                        if(beforeMethod.getParameterCount()==0){
                                            beforeMethod.invoke(myAspect);
                                        } else if(beforeMethod.getParameterCount()==1){
                                            // 如果带参数 => 判断参数类型
                                            // 简单处理只有一个 JoinPoint 参数的情况
                                            JoinPoint joinPoint = new JoinPoint() {
                                                @Override
                                                public String toShortString() {
                                                    return method.getName();
                                                }

                                                @Override
                                                public String toLongString() {
                                                    return method.toString();
                                                }

                                                @Override
                                                public Object getThis() {
                                                    return null;
                                                }

                                                @Override
                                                public Object getTarget() {
                                                    return bean;
                                                }

                                                @Override
                                                public Object[] getArgs() {
                                                    return args;
                                                }

                                                @Override
                                                public Signature getSignature() {
                                                    return null;
                                                }

                                                @Override
                                                public SourceLocation getSourceLocation() {
                                                    return null;
                                                }

                                                @Override
                                                public String getKind() {
                                                    return null;
                                                }

                                                @Override
                                                public StaticPart getStaticPart() {
                                                    return null;
                                                }
                                            };
                                            beforeMethod.invoke(myAspect, joinPoint);
                                        }
                                    } catch (Exception e) {
                                        throw new RuntimeException("前置增强方法执行失败!",e);
                                    }
                                });
                        Object ret = method.invoke(bean, args);
                        // 返回增强
                        return ret;
                    } catch (Throwable t){
                        // 异常增强
                        throw t;
                    } finally {
                        // 后置增强
                    }
                });
        // 替换原有的 bean
        singletonObjects.put(id,proxyBean);
    }

    // 判断该方法是否被切到
    public boolean isPointcut(String pointcut, Method method){
        // 注意: 只处理 execution 表达式
        // 去掉 execution()
        pointcut = pointcut.replaceAll("execution\\((.+)\\)", "$1");
        // 转换为正则表达式
        pointcut = pointcut.replaceAll("\\*|\\.\\.",".*");
        pointcut = pointcut.replaceAll("\\(","\\\\(");
        pointcut = pointcut.replaceAll("\\)","\\\\)");
        // System.out.println("*************** pointcut = " + pointcut);
        String methodString = method.toString();
        // 判断是否被切到
        return methodString.matches(pointcut);
    }

    public static void main(String[] args) throws NoSuchMethodException {
        AopApplicationContext cxt = new AopApplicationContext(IocConfig.class);
        System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
        cxt.pointcutMap.forEach((pointcut,list)->{
            System.out.println(pointcut);
            System.out.println(list);
        });
        System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
        cxt.proxyBeanMap.forEach((id,bean)->{
            System.out.println(id);
            System.out.println(bean);
        });
        System.out.println("$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$");
        UserDao userDaoImpl = (UserDao) cxt.getBean("userDaoImpl");
        userDaoImpl.insert(null);
        userDaoImpl.update(null);
        userDaoImpl.delete(null);
    }


}
